/*
 * @(#) $Header$
 *
 * Copyright (C) 2010  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.crypto.md;

import java.security.spec.AlgorithmParameterSpec;
import ca.forklabs.crypto.Parametrizable;
import ca.forklabs.crypto.spec.HAVALspec;

/**
 * This class embodies the algorithm for the message digest HAVAL according to:
 * <blockquote>
 * Yuliang Zheng, Josef Pieprzyk Jennifer Seberry,
 * <i>HAVAL -- A One-Way Hashing Algorithm with Variable Length of Output</i>,
 * Lecture Notes in Computer
 * Science, Vol. 718, Springer-Verlag, 1993, pp.83-104.
 * </blockquote>
 * It is more elegant to get an instance with the factory method in class
 * {@link java.security.MessageDigest} :
 * <blockquote>
 * <code>MessageDigest haval = MessageDigest.getInstance(Fork.HAVAL, Fork.NAME);</code>
 * </blockquote>
 * The default parameters are 5 passes and 256 bits hash length. They can be
 * changed as shown:
 * <blockquote>
 * <code>((Parametrizable) haval).setParameters(new HAVALspec(4, 192));</code>
 * </blockquote>
 * for a 4 passes 192 bits hash.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.crypto.md.HAVALéonard</a>
 * @version $Revision$
 * @see      ca.forklabs.crypto.Forklabs
 * @see      ca.forklabs.crypto.Parametrizable
 * @see      ca.forklabs.crypto.spec.HAVALspec
 */
public class HAVAL extends AbstractBlockMessageDigest implements Parametrizable {

//---------------------------
// Class variables
//---------------------------

   /** The name of this message digest. */
   @SuppressWarnings("nls")
   public static final String NAME = "HAVAL";

   /** Mnemonic for the 128 bits HAVAL. */
   public static final int HASH_LEN128 = 16;
   /** Mnemonic for the 160 bits HAVAL. */
   public static final int HASH_LEN160 = 20;
   /** Mnemonic for the 192 bits HAVAL. */
   public static final int HASH_LEN192 = 24;
   /** Mnemonic for the 224 bits HAVAL. */
   public static final int HASH_LEN224 = 28;
   /** Mnemonic for the 256 bits HAVAL. */
   public static final int HASH_LEN256 = 32;

   /** The length of the buffer. */
   public static final int BUFFER_LEN = 128;

   /** The version. */
   public static final int VERSION = 1;


//----------------------------
// Instance variables
//----------------------------

   /** Tailoring buffer. */
   private int[] D = new int[8];

   /** Scrambling buffer. */
   private int[] B = new int[32];

   /** The default number of passes. */
   private int passes = 5;

   /** The default digest length. */
   private int digest_bits = 256;


//----------------------------
// Constructor
//----------------------------

   /**
    *  Constructor.
    */
   public HAVAL() {
      super(HAVAL.NAME, HAVAL.BUFFER_LEN, HAVAL.HASH_LEN256);
      this.coreInit();
      }


//---------------------------
// Accessors and mutators
//---------------------------

   /**
    * Gets the tailoring buffer D.
    * @return   the tailoring buffer.
    */
   protected int[] getTailoringBuffer() {
      return this.D;
      }

   /**
    * Gets the scrambling buffer B.
    * @return   the scrambling buffer.
    */
   protected int[] getScramblingBuffer() {
      return this.B;
      }

   /**
    * Changes the number of passes.
    * @param   passes   the new number of passes.
    */
   protected void setPasses(int passes) {
      this.passes = passes;
      }

   /**
    * Gets the number of passes.
    * @return   the number of passes.
    */
   protected int getPasses() {
      return this.passes;
      }

   /**
    * Changes the size of the digest.
    * @param   bits   the new size of the digest.
    */
   protected void setDigestBits(int bits) {
      this.digest_bits = bits;
      }

   /**
    * Gets the size of the digest.
    * @return   the size of the digest.
    */
   protected int getDigestBits() {
      return this.digest_bits;
      }


//---------------------------
// Implemented methods from fork.crypto.Parametrizable
//---------------------------

   /**
    * Changes the default parameters to those passed in parameter.
    * @param   param   the new parameters.
    * @see   ca.forklabs.crypto.spec.HAVALspec
    */
   @Override
   @SuppressWarnings("hiding")
   public void setParameters(AlgorithmParameterSpec param) {
      if (false == param instanceof HAVALspec) {
         Class<?> expected = HAVALspec.class;
         Class<?> is = param.getClass();
         String message = this.getBadSpecClassErrorMessage(expected, is);
         throw new IllegalArgumentException(message);
         }

      HAVALspec spec = (HAVALspec) param;
      int digest_bits = spec.getLength();
      int passes = spec.getPasses();

      int digest_len;
      switch (digest_bits) {
         case 128:
            digest_len = HAVAL.HASH_LEN128;
            break;
         case 160:
            digest_len = HAVAL.HASH_LEN160;
            break;
         case 192:
            digest_len = HAVAL.HASH_LEN192;
            break;
         case 224:
            digest_len = HAVAL.HASH_LEN224;
            break;
         case 256:
            digest_len = HAVAL.HASH_LEN256;
            break;
      // pokemon, gotta catch them all
         default:
            String message = this.getBadHashLengthErrorMessage(digest_bits);
            throw new IllegalArgumentException(message);
         }
      this.setDigestBits(digest_bits);

      if ((passes != 3) && (passes != 4) && (passes != 5)) {
         String message = HAVAL.getBadPassesErrorMessage(passes);
         throw new IllegalArgumentException(message);
         }
      this.setPasses(passes);

      byte[] buffer = new byte[HAVAL.BUFFER_LEN];
      this.setBuffer(buffer);
      this.setDigestLen(digest_len);

      this.coreInit();
      }

   /**
    * Gest an <code>HAVALspec</code> initialized to the current number of passes
    * and hash length.
    * @return   the current parameters.
    * @see      ca.forklabs.crypto.spec.HAVALspec
    */
   @Override
   @SuppressWarnings("hiding")
   public AlgorithmParameterSpec getParameters() {
      int passes = this.getPasses();
      int len = this.getDigestBits();
      AlgorithmParameterSpec specs = new HAVALspec(passes, len);
      return specs;
      }


//----------------------------
// HAVAL scrambling methods
//----------------------------

   private static int f_1(int x6, int x5, int x4, int x3, int x2, int x1, int x0) {
   // x1x4 + x2x5 + x3x6 + x0x1 + x0
      return ((x1 & (x0 ^ x4)) ^ (x2 & x5) ^ (x3 & x6) ^ x0);
      }

   private static int f_2(int x6, int x5, int x4, int x3, int x2, int x1, int x0) {
   // x1x2x3 + x2x4x5 + x1x2 + x1x4 + x2x6 + x3x5 + x4x5 + x0x2 + x0
      return ((x2 & ((x1 & ~x3) ^ (x4 & x5) ^ x6 ^ x0)) ^ (x4 & (x1 ^ x5)) ^ (x3 & x5) ^ x0);
      }

   private static int f_3(int x6, int x5, int x4, int x3, int x2, int x1, int x0) {
   // x1x2x3 + x1x4 + x2x5 + x3x6 + x0x3 + x0
      return (x3 & ((x1 & x2) ^ x6 ^ x0) ^ (x1 & x4) ^ (x2 & x5) ^ x0);
      }

   private static int f_4(int x6, int x5, int x4, int x3, int x2, int x1, int x0) {
   // x1x2x3 + x2x4x5 + x3x4x6 + x1x4 + x2x6 + x3x4 + x3x5 + x3x6 + x4x5 + x4x6 + x0x4 + x0
      return ((x4 & ((x5 & ~x2) ^ (x3 & ~x6) ^ x1 ^ x6 ^ x0)) ^ (x3 & ((x1 & x2) ^ x5 ^ x6)) ^ (x2 & x6) ^ x0);
      }

   private static int f_5(int x6, int x5, int x4, int x3, int x2, int x1, int x0) {
   // x1x4 + x2x5 + x3x6 + x0x1x2x3 + x0x5 + x0
      return  (x0 & ((x1 & x2 & x3) ^ ~x5) ^ (x1 & x4) ^ (x2 & x5) ^ (x3 & x6));
      }

/*
 *           x_6 x_5 x_4 x_3 x_2 x_1 x_0
 *            |   |   |   |   |   |   |
 *            v   v   v   v   v   v   v
 * phi(3,1)  x_1 x_0 x_3 x_5 x_6 x_2 x_4
 *
 * phi(3,2)  x_4 x_2 x_1 x_0 x_5 x_3 x_6
 *
 * phi(3,3)  x_6 x_1 x_2 x_3 x_4 x_5 x_0
 * -------------------------------------
 * phi(4,1)  x_2 x_6 x_1 x_4 x_5 x_3 x_0
 *
 * phi(4,2)  x_3 x_5 x_2 x_0 x_1 x_6 x_4
 *
 * phi(4,3)  x_1 x_4 x_3 x_6 x_0 x_2 x_5
 *
 * phi(4,4)  x_6 x_4 x_0 x_5 x_2 x_1 x_3
 * -------------------------------------
 * phi(5,1)  x_3 x_4 x_1 x_0 x_5 x_2 x_6
 *
 * phi(5,2)  x_6 x_2 x_1 x_0 x_3 x_4 x_5
 *
 * phi(5,3)  x_2 x_6 x_0 x_4 x_3 x_1 x_5
 *
 * phi(5,4)  x_1 x_5 x_3 x_2 x_0 x_4 x_6
 *
 * phi(5,5)  x_2 x_5 x_0 x_6 x_4 x_3 x_1
 */
   private static int FF_1(int passes, int x7, int x6, int x5, int x4, int x3, int x2, int x1, int x0, int w) {
      int t;
      switch (passes) {
         case 3:
            t = HAVAL.f_1(x1, x0, x3, x5, x6, x2, x4);
            break;
         case 4:
            t = HAVAL.f_1(x2, x6, x1, x4, x5, x3, x0);
            break;
         case 5:
            t = HAVAL.f_1(x3, x4, x1, x0, x5, x2, x6);
            break;
         default:
         // this should never happen as the pass value has already been checked
            String message = HAVAL.getBadPassesErrorMessage(passes);
            throw new IllegalStateException(message);
        }

      return AbstractBlockMessageDigest.rotateLeft(t, 25) + AbstractBlockMessageDigest.rotateLeft(x7, 21) + w;
      }

   private static int FF_2 (int passes, int x7, int x6, int x5, int x4, int x3, int x2, int x1, int x0, int w, int c) {
      int t;
      switch (passes) {
         case 3:
            t = HAVAL.f_2(x4, x2, x1, x0, x5, x3, x6);
            break;
         case 4:
            t = HAVAL.f_2(x3, x5, x2, x0, x1, x6, x4);
            break;
         case 5:
            t = HAVAL.f_2(x6, x2, x1, x0, x3, x4, x5);
            break;
         default:
         // this should never happen as the pass value has already been checked
            String message = HAVAL.getBadPassesErrorMessage(passes);
            throw new IllegalStateException(message);
         }
      return AbstractBlockMessageDigest.rotateLeft(t, 25) + AbstractBlockMessageDigest.rotateLeft(x7, 21) + w + c;
      }

   private static int FF_3 (int passes, int x7, int x6, int x5, int x4, int x3, int x2, int x1, int x0, int w, int c) {
      int t;
      switch (passes) {
         case 3:
            t = HAVAL.f_3(x6, x1, x2, x3, x4, x5, x0);
            break;
         case 4:
            t = HAVAL.f_3(x1, x4, x3, x6, x0, x2, x5);
            break;
         case 5:
            t = HAVAL.f_3(x2, x6, x0, x4, x3, x1, x5);
            break;
         default:
         // this should never happen as the pass value has already been checked
            String message = HAVAL.getBadPassesErrorMessage(passes);
            throw new IllegalStateException(message);
         }
      return AbstractBlockMessageDigest.rotateLeft(t, 25) + AbstractBlockMessageDigest.rotateLeft(x7, 21) + w + c;
      }

   private static int FF_4 (int passes, int x7, int x6, int x5, int x4, int x3, int x2, int x1, int x0, int w, int c) {
      int t;
      switch (passes) {
         case 4:
            t = HAVAL.f_4(x6, x4, x0, x5, x2, x1, x3);
            break;
         case 5:
            t = HAVAL.f_4(x1, x5, x3, x2, x0, x4, x6);
            break;
         case 3:
         default:
         // this should never happen as the pass value has already been checked
            String message = HAVAL.getBadPassesErrorMessage(passes);
            throw new IllegalStateException(message);
        }
      return AbstractBlockMessageDigest.rotateLeft(t, 25) + AbstractBlockMessageDigest.rotateLeft(x7, 21) + w + c;
    }

    private static int FF_5 (int passes, int x7, int x6, int x5, int x4, int x3, int x2, int x1, int x0, int w, int c) {
      int t = 0;
      switch (passes) {
         case 5:
            t = HAVAL.f_5(x2, x5, x0, x6, x4, x3, x1);
            break;
         case 3:
         case 4:
         default:
         // this should never happen as the pass value has already been checked
            String message = HAVAL.getBadPassesErrorMessage(passes);
            throw new IllegalStateException(message);
         }
      return AbstractBlockMessageDigest.rotateLeft(t, 25) + AbstractBlockMessageDigest.rotateLeft(x7, 21) + w + c;
      }

   private byte[] tailor() {
      int temp = 0;
      int bits = this.getDigestBits();
      int[] tailor = this.getTailoringBuffer();
      switch (bits) {
         case 128:
            temp = (tailor[7] & 0x000000ff) | (tailor[6] & 0xff000000) | (tailor[5] & 0x00ff0000) | (tailor[4] & 0x0000ff00);
            tailor[0] += AbstractBlockMessageDigest.rotateLeft(temp, 24);
            temp = (tailor[7] & 0x0000ff00) | (tailor[6] & 0x000000ff) | (tailor[5] & 0xff000000) | (tailor[4] & 0x00ff0000);
            tailor[1] += AbstractBlockMessageDigest.rotateLeft(temp, 16);
            temp = (tailor[7] & 0x00ff0000) | (tailor[6] & 0x0000ff00) | (tailor[5] & 0x000000ff) | (tailor[4] & 0xff000000);
            tailor[2] += AbstractBlockMessageDigest.rotateLeft(temp, 8);
            temp = (tailor[7] & 0xff000000) | (tailor[6] & 0x00ff0000) | (tailor[5] & 0x0000ff00) | (tailor[4] & 0x000000ff);
            tailor[3] += temp;
            break;

         case 160:
            temp = (tailor[7] & 0x0000003f) | (tailor[6] & (0x0000007f << 25)) | (tailor[5] & (0x0000003f << 19));
            tailor[0] += AbstractBlockMessageDigest.rotateLeft(temp, 13);
            temp = (tailor[7] & (0x0000003f <<  6)) | (tailor[6] & 0x0000003f) | (tailor[5] & (0x0000007f << 25));
            tailor[1] += AbstractBlockMessageDigest.rotateLeft(temp, 7);
            temp = (tailor[7] & (0x0000007f << 12)) | (tailor[6] & (0x0000003f <<  6)) | (tailor[5] & 0x0000003f);
            tailor[2] += temp;
            temp = (tailor[7] & (0x0000003f << 19)) | (tailor[6] & (0x0000007f << 12)) | (tailor[5] & (0x0000003f <<  6));
            tailor[3] += temp >>> 6;
            temp = (tailor[7] & (0x0000007f << 25)) | (tailor[6] & (0x0000003f << 19)) | (tailor[5] & (0x0000007f << 12));
            tailor[4] += temp >>> 12;
            break;

         case 192:
            temp = (tailor[7] & 0x0000001f) | (tailor[6] & (0x0000003f << 26));
            tailor[0] += AbstractBlockMessageDigest.rotateLeft(temp, 6);
            temp = (tailor[7] & (0x0000001f <<  5)) | (tailor[6] & 0x0000001f);
            tailor[1] += temp;
            temp = (tailor[7] & (0x0000003f << 10)) | (tailor[6] & (0x0000001f <<  5));
            tailor[2] += temp >>> 5;
            temp = (tailor[7] & (0x0000001f << 16)) | (tailor[6] & (0x0000003f << 10));
            tailor[3] += temp >>> 10;
            temp = (tailor[7] & (0x0000001f << 21)) | (tailor[6] & (0x0000001f << 16));
            tailor[4] += temp >>> 16;
            temp = (tailor[7] & (0x0000003f << 26)) | (tailor[6] & (0x0000001f << 21));
            tailor[5] += temp >>> 21;
            break;

         case 224:
            tailor[0] += (tailor[7] >>> 27) & 0x0000001f;
            tailor[1] += (tailor[7] >>> 22) & 0x0000001f;
            tailor[2] += (tailor[7] >>> 18) & 0x0000000f;
            tailor[3] += (tailor[7] >>> 13) & 0x0000001f;
            tailor[4] += (tailor[7] >>>  9) & 0x0000000f;
            tailor[5] += (tailor[7] >>>  4) & 0x0000001f;
            tailor[6] +=  tailor[7]         & 0x0000000f;
            break;

         case 256:
            break;

         default:
         // this should never happen as the bits value has already been checked
            String message = this.getBadHashLengthErrorMessage(bits);
            throw new IllegalStateException(message);
         }

      int digest_len = this.getDigestLen();
      byte[] digest = new byte[digest_len];
      for (int i = 0, j = 0; j < digest_len; i++) {
         j = AbstractBlockMessageDigest.breakIntLE(tailor[i], digest, j);
         }

      this.coreInit();

      return digest;
      }


//----------------------------
// HAVAL core methods
//----------------------------

   @Override
   @SuppressWarnings("hiding")
   protected void coreTransform() {
      int[] b = this.getScramblingBuffer();
      byte[] buffer = this.getBuffer();

      for (int i = 0, j = 0; i < 32; i++) {
         int least = j; j++;
         int less =  j; j++;
         int more =  j; j++;
         int most =  j; j++;
         b[i] = AbstractBlockMessageDigest.makeInt(buffer, most, more, less, least);
         }

      AbstractBlockMessageDigest.zeroize(buffer);

      int[] tailor = this.getTailoringBuffer();
      int t0 = tailor[0];
      int t1 = tailor[1];
      int t2 = tailor[2];
      int t3 = tailor[3];
      int t4 = tailor[4];
      int t5 = tailor[5];
      int t6 = tailor[6];
      int t7 = tailor[7];

      int passes = this.getPasses();

   // pass 1
      t7 = HAVAL.FF_1(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[ 0]);
      t6 = HAVAL.FF_1(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 1]);
      t5 = HAVAL.FF_1(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[ 2]);
      t4 = HAVAL.FF_1(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[ 3]);
      t3 = HAVAL.FF_1(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[ 4]);
      t2 = HAVAL.FF_1(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[ 5]);
      t1 = HAVAL.FF_1(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[ 6]);
      t0 = HAVAL.FF_1(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[ 7]);
      t7 = HAVAL.FF_1(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[ 8]);
      t6 = HAVAL.FF_1(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 9]);
      t5 = HAVAL.FF_1(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[10]);
      t4 = HAVAL.FF_1(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[11]);
      t3 = HAVAL.FF_1(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[12]);
      t2 = HAVAL.FF_1(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[13]);
      t1 = HAVAL.FF_1(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[14]);
      t0 = HAVAL.FF_1(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[15]);
      t7 = HAVAL.FF_1(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[16]);
      t6 = HAVAL.FF_1(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[17]);
      t5 = HAVAL.FF_1(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[18]);
      t4 = HAVAL.FF_1(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[19]);
      t3 = HAVAL.FF_1(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[20]);
      t2 = HAVAL.FF_1(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[21]);
      t1 = HAVAL.FF_1(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[22]);
      t0 = HAVAL.FF_1(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[23]);
      t7 = HAVAL.FF_1(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[24]);
      t6 = HAVAL.FF_1(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[25]);
      t5 = HAVAL.FF_1(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[26]);
      t4 = HAVAL.FF_1(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[27]);
      t3 = HAVAL.FF_1(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[28]);
      t2 = HAVAL.FF_1(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[29]);
      t1 = HAVAL.FF_1(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[30]);
      t0 = HAVAL.FF_1(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[31]);
   // pass 2
      t7 = HAVAL.FF_2(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[ 5], 0x452821e6);
      t6 = HAVAL.FF_2(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[14], 0x38d01377);
      t5 = HAVAL.FF_2(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[26], 0xbe5466cf);
      t4 = HAVAL.FF_2(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[18], 0x34e90c6c);
      t3 = HAVAL.FF_2(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[11], 0xc0ac29b7);
      t2 = HAVAL.FF_2(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[28], 0xc97c50dd);
      t1 = HAVAL.FF_2(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[ 7], 0x3f84d5b5);
      t0 = HAVAL.FF_2(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[16], 0xb5470917);
      t7 = HAVAL.FF_2(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[ 0], 0x9216d5d9);
      t6 = HAVAL.FF_2(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[23], 0x8979fb1b);
      t5 = HAVAL.FF_2(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[20], 0xd1310ba6);
      t4 = HAVAL.FF_2(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[22], 0x98dfb5ac);
      t3 = HAVAL.FF_2(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[ 1], 0x2ffd72db);
      t2 = HAVAL.FF_2(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[10], 0xd01adfb7);
      t1 = HAVAL.FF_2(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[ 4], 0xb8e1afed);
      t0 = HAVAL.FF_2(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[ 8], 0x6a267e96);
      t7 = HAVAL.FF_2(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[30], 0xba7c9045);
      t6 = HAVAL.FF_2(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 3], 0xf12c7f99);
      t5 = HAVAL.FF_2(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[21], 0x24a19947);
      t4 = HAVAL.FF_2(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[ 9], 0xb3916cf7);
      t3 = HAVAL.FF_2(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[17], 0x0801f2e2);
      t2 = HAVAL.FF_2(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[24], 0x858efc16);
      t1 = HAVAL.FF_2(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[29], 0x636920d8);
      t0 = HAVAL.FF_2(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[ 6], 0x71574e69);
      t7 = HAVAL.FF_2(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[19], 0xa458fea3);
      t6 = HAVAL.FF_2(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[12], 0xf4933d7e);
      t5 = HAVAL.FF_2(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[15], 0x0d95748f);
      t4 = HAVAL.FF_2(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[13], 0x728eb658);
      t3 = HAVAL.FF_2(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[ 2], 0x718bcd58);
      t2 = HAVAL.FF_2(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[25], 0x82154aee);
      t1 = HAVAL.FF_2(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[31], 0x7b54a41d);
      t0 = HAVAL.FF_2(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[27], 0xc25a59b5);
   // pass 3
      t7 = HAVAL.FF_3(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[19], 0x9c30d539);
      t6 = HAVAL.FF_3(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 9], 0x2af26013);
      t5 = HAVAL.FF_3(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[ 4], 0xc5d1b023);
      t4 = HAVAL.FF_3(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[20], 0x286085f0);
      t3 = HAVAL.FF_3(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[28], 0xca417918);
      t2 = HAVAL.FF_3(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[17], 0xb8db38ef);
      t1 = HAVAL.FF_3(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[ 8], 0x8e79dcb0);
      t0 = HAVAL.FF_3(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[22], 0x603a180e);
      t7 = HAVAL.FF_3(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[29], 0x6c9e0e8b);
      t6 = HAVAL.FF_3(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[14], 0xb01e8a3e);
      t5 = HAVAL.FF_3(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[25], 0xd71577c1);
      t4 = HAVAL.FF_3(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[12], 0xbd314b27);
      t3 = HAVAL.FF_3(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[24], 0x78af2fda);
      t2 = HAVAL.FF_3(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[30], 0x55605c60);
      t1 = HAVAL.FF_3(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[16], 0xe65525f3);
      t0 = HAVAL.FF_3(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[26], 0xaa55ab94);
      t7 = HAVAL.FF_3(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[31], 0x57489862);
      t6 = HAVAL.FF_3(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[15], 0x63e81440);
      t5 = HAVAL.FF_3(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[ 7], 0x55ca396a);
      t4 = HAVAL.FF_3(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[ 3], 0x2aab10b6);
      t3 = HAVAL.FF_3(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[ 1], 0xb4cc5c34);
      t2 = HAVAL.FF_3(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[ 0], 0x1141e8ce);
      t1 = HAVAL.FF_3(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[18], 0xa15486af);
      t0 = HAVAL.FF_3(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[27], 0x7c72e993);
      t7 = HAVAL.FF_3(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[13], 0xb3ee1411);
      t6 = HAVAL.FF_3(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 6], 0x636fbc2a);
      t5 = HAVAL.FF_3(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[21], 0x2ba9c55d);
      t4 = HAVAL.FF_3(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[10], 0x741831f6);
      t3 = HAVAL.FF_3(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[23], 0xce5c3e16);
      t2 = HAVAL.FF_3(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[11], 0x9b87931e);
      t1 = HAVAL.FF_3(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[ 5], 0xafd6ba33);
      t0 = HAVAL.FF_3(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[ 2], 0x6c24cf5c);
   // pass 4
      if (4 == passes || 5 == passes) {
         t7 = HAVAL.FF_4(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[24], 0x7a325381);
         t6 = HAVAL.FF_4(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 4], 0x28958677);
         t5 = HAVAL.FF_4(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[ 0], 0x3b8f4898);
         t4 = HAVAL.FF_4(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[14], 0x6b4bb9af);
         t3 = HAVAL.FF_4(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[ 2], 0xc4bfe81b);
         t2 = HAVAL.FF_4(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[ 7], 0x66282193);
         t1 = HAVAL.FF_4(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[28], 0x61d809cc);
         t0 = HAVAL.FF_4(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[23], 0xfb21a991);
         t7 = HAVAL.FF_4(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[26], 0x487cac60);
         t6 = HAVAL.FF_4(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 6], 0x5dec8032);
         t5 = HAVAL.FF_4(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[30], 0xef845d5d);
         t4 = HAVAL.FF_4(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[20], 0xe98575b1);
         t3 = HAVAL.FF_4(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[18], 0xdc262302);
         t2 = HAVAL.FF_4(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[25], 0xeb651b88);
         t1 = HAVAL.FF_4(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[19], 0x23893e81);
         t0 = HAVAL.FF_4(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[ 3], 0xd396acc5);
         t7 = HAVAL.FF_4(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[22], 0x0f6d6ff3);
         t6 = HAVAL.FF_4(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[11], 0x83f44239);
         t5 = HAVAL.FF_4(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[31], 0x2e0b4482);
         t4 = HAVAL.FF_4(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[21], 0xa4842004);
         t3 = HAVAL.FF_4(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[ 8], 0x69c8f04a);
         t2 = HAVAL.FF_4(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[27], 0x9e1f9b5e);
         t1 = HAVAL.FF_4(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[12], 0x21c66842);
         t0 = HAVAL.FF_4(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[ 9], 0xf6e96c9a);
         t7 = HAVAL.FF_4(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[ 1], 0x670c9c61);
         t6 = HAVAL.FF_4(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[29], 0xabd388f0);
         t5 = HAVAL.FF_4(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[ 5], 0x6a51a0d2);
         t4 = HAVAL.FF_4(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[15], 0xd8542f68);
         t3 = HAVAL.FF_4(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[17], 0x960fa728);
         t2 = HAVAL.FF_4(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[10], 0xab5133a3);
         t1 = HAVAL.FF_4(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[16], 0x6eef0b6c);
         t0 = HAVAL.FF_4(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[13], 0x137a3be4);
         }
   // pass 5
      if (5 == passes) {
         t7 = HAVAL.FF_5(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[27], 0xba3bf050);
         t6 = HAVAL.FF_5(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 3], 0x7efb2a98);
         t5 = HAVAL.FF_5(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[21], 0xa1f1651d);
         t4 = HAVAL.FF_5(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[26], 0x39af0176);
         t3 = HAVAL.FF_5(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[17], 0x66ca593e);
         t2 = HAVAL.FF_5(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[11], 0x82430e88);
         t1 = HAVAL.FF_5(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[20], 0x8cee8619);
         t0 = HAVAL.FF_5(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[29], 0x456f9fb4);
         t7 = HAVAL.FF_5(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[19], 0x7d84a5c3);
         t6 = HAVAL.FF_5(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 0], 0x3b8b5ebe);
         t5 = HAVAL.FF_5(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[12], 0xe06f75d8);
         t4 = HAVAL.FF_5(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[ 7], 0x85c12073);
         t3 = HAVAL.FF_5(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[13], 0x401a449f);
         t2 = HAVAL.FF_5(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[ 8], 0x56c16aa6);
         t1 = HAVAL.FF_5(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[31], 0x4ed3aa62);
         t0 = HAVAL.FF_5(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[10], 0x363f7706);
         t7 = HAVAL.FF_5(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[ 5], 0x1bfedf72);
         t6 = HAVAL.FF_5(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[ 9], 0x429b023d);
         t5 = HAVAL.FF_5(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[14], 0x37d0d724);
         t4 = HAVAL.FF_5(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[30], 0xd00a1248);
         t3 = HAVAL.FF_5(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[18], 0xdb0fead3);
         t2 = HAVAL.FF_5(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[ 6], 0x49f1c09b);
         t1 = HAVAL.FF_5(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[28], 0x075372c9);
         t0 = HAVAL.FF_5(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[24], 0x80991b7b);
         t7 = HAVAL.FF_5(passes, t7, t6, t5, t4, t3, t2, t1, t0, b[ 2], 0x25d479d8);
         t6 = HAVAL.FF_5(passes, t6, t5, t4, t3, t2, t1, t0, t7, b[23], 0xf6e8def7);
         t5 = HAVAL.FF_5(passes, t5, t4, t3, t2, t1, t0, t7, t6, b[16], 0xe3fe501a);
         t4 = HAVAL.FF_5(passes, t4, t3, t2, t1, t0, t7, t6, t5, b[22], 0xb6794c3b);
         t3 = HAVAL.FF_5(passes, t3, t2, t1, t0, t7, t6, t5, t4, b[ 4], 0x976ce0bd);
         t2 = HAVAL.FF_5(passes, t2, t1, t0, t7, t6, t5, t4, t3, b[ 1], 0x04c006ba);
         t1 = HAVAL.FF_5(passes, t1, t0, t7, t6, t5, t4, t3, t2, b[25], 0xc1a94fb6);
         t0 = HAVAL.FF_5(passes, t0, t7, t6, t5, t4, t3, t2, t1, b[15], 0x409f60c4);
         }

      tailor[0] += t0;
      tailor[1] += t1;
      tailor[2] += t2;
      tailor[3] += t3;
      tailor[4] += t4;
      tailor[5] += t5;
      tailor[6] += t6;
      tailor[7] += t7;

      AbstractBlockMessageDigest.zeroize(b);
      }

   @Override
   protected void coreInit() {
      this.D[0] = 0x243f6a88;
      this.D[1] = 0x85a308d3;
      this.D[2] = 0x13198a2e;
      this.D[3] = 0x03707344;
      this.D[4] = 0xa4093822;
      this.D[5] = 0x299f31d0;
      this.D[6] = 0x082efa98;
      this.D[7] = 0xec4e6c89;
      }

   @Override
   @SuppressWarnings("hiding")
   protected byte[] coreDoFinal() {
      long count = this.getCount();
      long bits = count * 8L;

      int index = (int) (count % HAVAL.BUFFER_LEN);
      int padlen = (index < (HAVAL.BUFFER_LEN - 10)) ? (HAVAL.BUFFER_LEN - 10) - index : (2 * HAVAL.BUFFER_LEN - 10) - index;
      byte[] last = new byte[padlen + 10];

      int digest_bits = this.getDigestBits();
      int passes = this.getPasses();

      last[0] = (byte) 0x01;
      last[padlen++] = (byte) (((digest_bits & 0x00000003) << 6) | ((passes & 0x00000007) << 3) | (HAVAL.VERSION & 0x00000007));
      last[padlen++] = (byte) ((digest_bits >> 2) & 0xff);
      padlen = AbstractBlockMessageDigest.breakLongLE(bits, last, padlen);

      this.engineUpdate(last, 0, padlen);

      byte[] digest = this.tailor();
      return digest;
      }


//---------------------------
// Implemented method from java.lang.Cloneable
//---------------------------

   /**
    * Makes a deep copy this message digest in its current state.
    * @return   a clone of this message digest.
    */
   @Override
   public Object clone() throws CloneNotSupportedException {
      HAVAL haval = (HAVAL) super.clone();
      haval.D = this.D.clone();
      haval.B = this.B.clone();
      return haval;
      }


//---------------------------
// Error methods
//---------------------------

   /**
    * Gets the formatted error message saying the hash length is bad.
    * @param   bits   the bad hash length.
    * @return   the formatted message.
    */
   @SuppressWarnings("boxing")
   protected String getBadHashLengthErrorMessage(int bits) {
      String key = Resources.HAVAL_BAD_HASH_LEN;
      String message = Resources.getLocalizedString(key, bits);
      return message;
      }

   /**
    * Gets the formatted error message saying the the number of passes is bad.
    * @param   passes   the bad number of passes.
    * @return   the formatted message.
    */
   @SuppressWarnings("boxing")
   protected static String getBadPassesErrorMessage(int passes) {
      String key = Resources.HAVAL_BAD_PASSES;
      String message = Resources.getLocalizedString(key, passes);
      return message;
      }

   }
